﻿using Microsoft.Ccr.Core;
using Microsoft.Dss.Core;
using Microsoft.Dss.Core.Attributes;
using Microsoft.Dss.Core.DsspHttpUtilities;
using dssphttp = Microsoft.Dss.Core.DsspHttp;
using Microsoft.Dss.ServiceModel.Dssp;
using Microsoft.Dss.ServiceModel.DsspServiceBase;
using submgr = Microsoft.Dss.Services.SubscriptionManager;
using Microsoft.Ccr.Core;
using pxanalogsensor = Microsoft.Robotics.Services.AnalogSensor.Proxy;
using Microsoft.Robotics.PhysicalModel.Proxy;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Net;
using System.Xml.Serialization;
using W3C.Soap;
using vex = VEXRobotics.Proxy;


namespace VEXService.AnalogInput
{
    /// <summary>
    /// VEXAnalogInput Contract
    /// </summary>
    public sealed class Contract
    {
        /// The Unique Contract Identifier for the VEXAnalogInput service
        [DataMember, Description("Identifies the Unique Contract Identifier for the VEXAnalogInput service.")]
        public const String Identifier = "http://schemas.vexrobotics.com/2011/09/vexservice.analoginput.html";
    }

    /// <summary>
    /// VEX Analog Input Sensor State
    /// </summary>
    [DataContract, Description("Specifies the VEX Analog Input state.")]
    public class AnalogInputState : IDssSerializable, ICloneable
    {
        private string _Name;
        private vex.SensorPort _SensorPort;
        private int _RawMeasurement;
        private double _NormalizedMeasurement;
        private DateTime _TimeStamp;
        private Pose _Pose;

        public AnalogInputState()
        {
            _TimeStamp = DateTime.MinValue;
            _Pose = new Pose();
        }

        /// <summary>
        /// VEX Sensor Port
        /// </summary>
        [DataMember]
        [Description("Specifies the VEX Sensor Port.")]
        public vex.SensorPort SensorPort
        {
            get { return _SensorPort; }
            set { _SensorPort = value; }
        }

        /// <summary>
        /// The name of this Analog Input Sensor instance
        /// </summary>
        [DataMember]
        [Description("Specifies a user friendly name for the VEX Analog Input.")]
        public string Name
        {
            get { return _Name; }
            set { _Name = value; }
        }

        //
        // Summary:
        //     Normalized measurement relative to the RawMeasurementRange property. This
        //     value is in the range from 0 to 1.
        [DataMember(Order = -1)]
        [Browsable(false)]
        [Description(@"Provides the normalized input value (relative to the RawMeasurementRange property).  This value is in the range from 0 to 1.")]
        public double NormalizedMeasurement
        {
            get { return _NormalizedMeasurement; }
            set { _NormalizedMeasurement = value; }
        }

        //
        // Summary:
        //     The analog raw measurement.
        [Description("Provides the raw input value from the sensor.")]
        [Browsable(false)]
        [DataMember(Order = -1)]
        public int RawMeasurement
        {
            get { return _RawMeasurement; }
            set
            {
                _RawMeasurement = value;
            }
        }

        /// <summary>
        /// The time of the last sensor update
        /// </summary>
        [DefaultValue(typeof(DateTime), "0001-01-01T00:00:00")]
        [Description("Identifies the timestamp of the sensor reading.")]
        [Browsable(false)]
        [DataMember(Order = -1, XmlOmitDefaultValue = true)]
        public DateTime TimeStamp
        {
            get { return _TimeStamp; }
            set { _TimeStamp = value; }
        }

        /// <summary>
        /// Pose of the motor
        /// </summary>
        [DataMember]
        [Description("The position and orientation of the Analog Input sensor.")]
        public Pose Pose
        {
            get { return _Pose; }
            set { _Pose = value; }
        }


        /// <summary>
        /// Transform current state to the specified generic state.
        /// </summary>
        /// <param name="genericState"></param>
        /// <returns></returns>
        public pxanalogsensor.AnalogSensorState ToGenericState(pxanalogsensor.AnalogSensorState genericState)
        {
            genericState.HardwareIdentifier = (int) this.SensorPort;
            genericState.NormalizedMeasurement = this.NormalizedMeasurement;
            genericState.Pose = this.Pose;
            genericState.RawMeasurement = this.RawMeasurement;
            genericState.RawMeasurementRange = 1024.0;
            genericState.TimeStamp = this.TimeStamp;
            return genericState;
        }

        /// <summary>
        /// Copy generic state to this state, transforming data members.
        /// </summary>
        /// <param name="genericState"></param>
        public void FromGenericState(pxanalogsensor.AnalogSensorState genericState)
        {
            this.SensorPort = (vex.SensorPort)genericState.HardwareIdentifier;
            this.NormalizedMeasurement = genericState.NormalizedMeasurement;
            this.Pose = genericState.Pose;
            this.RawMeasurement = (int) genericState.RawMeasurement;
            this.TimeStamp = genericState.TimeStamp;
        }

        #region IDssSerializable

        /// <summary>
        /// Copy To Analog Input sensor state
        /// </summary>
        public virtual void CopyTo(IDssSerializable target)
        {
            AnalogInputState typedTarget = target as AnalogInputState;

            if (typedTarget == null)
                throw new ArgumentException("CopyTo({0}) requires type {0}", this.GetType().FullName);

            typedTarget.Name = this.Name;
            typedTarget.Pose = this.Pose;
            typedTarget.RawMeasurement = this.RawMeasurement;
            typedTarget.SensorPort = this.SensorPort;
            typedTarget.TimeStamp = this.TimeStamp;
        }
        /// <summary>
        /// Clone Analog Input sensor state
        /// </summary>
        public virtual object Clone()
        {
            AnalogInputState target = new AnalogInputState();

            target.Name = this.Name;
            target.Pose = this.Pose;
            target.RawMeasurement = this.RawMeasurement;
            target.SensorPort = this.SensorPort;
            target.TimeStamp = this.TimeStamp;

            return target;

        }
        /// <summary>
        /// Serialize
        /// </summary>
        public virtual void Serialize(System.IO.BinaryWriter writer)
        {
            writer.Write((int) SensorPort);
            writer.Write(Name);
            writer.Write(RawMeasurement);
            Pose.Serialize(writer);
            Microsoft.Dss.Services.Serializer.BinarySerializationHelper.SerializeDateTime(TimeStamp, writer);
        }

        /// <summary>
        /// Deserialize
        /// </summary>
        public virtual object Deserialize(System.IO.BinaryReader reader)
        {
            SensorPort = (vex.SensorPort)reader.ReadInt32();
            Name = reader.ReadString();
            RawMeasurement = reader.ReadInt32();
            Pose.Deserialize(reader);
            TimeStamp = Microsoft.Dss.Services.Serializer.BinarySerializationHelper.DeserializeDateTime(reader);

            return this;
        }

        #endregion
    }

    /// <summary>
    /// VEX Analog Input Sensor Configuration.
    /// </summary>
    [DataContract, Description("Specifies the VEX Analog Input Configuration.")]
    public class AnalogInputConfig
    {
        /// <summary>
        /// VEX Analog Input Sensor Configuration.
        /// </summary>
        public AnalogInputConfig() { }

        /// <summary>
        /// VEX Analog Input Sensor Configuration.
        /// </summary>
        /// <param name="sensorPort"></param>
        public AnalogInputConfig(vex.SensorPort sensorPort)
        {
            this.SensorPort = sensorPort;
        }

        /// <summary>
        /// The name of this Analog Input Sensor instance
        /// </summary>
        [DataMember, Description("Specifies a user friendly name for the VEX Analog Input.")]
        public string Name;

        /// <summary>
        /// VEX Sensor Port
        /// </summary>
        [DataMember, Description("Specifies the VEX Sensor Port.")]
        [DataMemberConstructor]
        public vex.SensorPort SensorPort;
    }

    /// <summary>
    /// VEX Analog Input Sensor Operations
    /// </summary>
    [ServicePort]
    public class VEXAnalogInputOperations : PortSet<
        DsspDefaultLookup,
        DsspDefaultDrop,
        dssphttp.HttpGet,
        dssphttp.HttpPost,
        Get,
        pxanalogsensor.Subscribe,
        pxanalogsensor.ReliableSubscribe,
        pxanalogsensor.Replace>
    {
    }

    /// <summary>
    /// Get the VEX Analog Input State
    /// </summary>
    [Description("Gets the current state of the VEX Analog Input Sensor.")]
    public class Get : Get<GetRequestType, PortSet<AnalogInputState, Fault>>
    {
    }

    /// <summary>
    /// VEX Analog Input Service
    /// </summary>
    [Contract(Contract.Identifier)]
    [AlternateContract(pxanalogsensor.Contract.Identifier)]
    [Description("Provides access to the VEX Analog Input.")]
    [DisplayName("VEX Analog Input")]
    [DssServiceDescription("http://msdn.microsoft.com/en-us/library/dd145244.aspx")]
    [DssCategory(vex.VEXCategories.VEX)]
    public class VEXAnalogInput : DsspServiceBase
    {
        /// <summary>
        /// State of the device
        /// </summary>
        [InitialStatePartner(Optional = true, ServiceUri = ServicePaths.Store + "/VEX.AnalogInput.config.xml")]
        private AnalogInputState _State = new AnalogInputState();

        /// <summary>
        /// _main Port
        /// </summary>
        [ServicePort("/VEX/AnalogInput", AllowMultipleInstances=true)]
        private VEXAnalogInputOperations _mainPort = new VEXAnalogInputOperations();
        [AlternateServicePort("/analogsensor", 
            AllowMultipleInstances = true, 
            AlternateContract = pxanalogsensor.Contract.Identifier)]
        private pxanalogsensor.AnalogSensorOperations _genericPort = new pxanalogsensor.AnalogSensorOperations();

        [EmbeddedResource("VEXService.Config.AnalogInput.xslt")]
        string _transform = string.Empty;
        
        /// <summary>
        /// Partner with the VEX
        /// </summary>
        [Partner("VEX",
            Contract = vex.Contract.Identifier,
            CreationPolicy = PartnerCreationPolicy.UseExistingOrCreate,
            Optional = false)]
        private vex.VEXOperations _vexPort = new vex.VEXOperations();
        private vex.VEXOperations _vexNotificationPort = new vex.VEXOperations();

        /// <summary>
        /// HTTP helper
        /// </summary>
        DsspHttpUtilitiesPort _httpUtilities = new DsspHttpUtilitiesPort();

        /// <summary>
        /// Subscription manager partner
        /// </summary>
        [Partner(Partners.SubscriptionManagerString, Contract = submgr.Contract.Identifier, CreationPolicy = PartnerCreationPolicy.CreateAlways)]
        submgr.SubscriptionManagerPort _subMgrPort = new submgr.SubscriptionManagerPort();

        /// <summary>
        /// Default Service Constructor
        /// </summary>
        public VEXAnalogInput(DsspServiceCreationPort creationPort) 
            : base(creationPort)
        {
        }

        /// <summary>
        /// Service Start
        /// </summary>
        protected override void Start()
        {
            LogVerbose(LogGroups.Console, "Configuring VEX Analog Input... ");

            #region Initialize Service State
            if (_State == null)
            {
                _State = new AnalogInputState();
                SaveState(_State);
            }
            #endregion

			base.Start();

            #region Initialize Service State
            // Reset the controller to know about our configuration
            Port<bool> donePort = new Port<bool>();
            SpawnIterator<Port<bool>>(donePort, InitializeKnownConnection);
            Activate(Arbiter.Receive(false, donePort,
                delegate(bool success)
                {
                    if (success)
                    {
                        // display HTTP service Uri
                        LogInfo(LogGroups.Console, "Service uri: ");
                    }
                    else
                    {
                        LogError(LogGroups.Console, "VEX Analog Input service failed to start.");
                        _mainPort.Post(new DsspDefaultDrop());
                    }
                }));
            #endregion

            // Needed for HttpPost
            _httpUtilities = DsspHttpUtilitiesService.Create(Environment);
        }

        /// <summary>
        /// Initialize this object based upon the current state.  Additionally,
        /// update the controller to our configuration
        /// </summary>
        /// <param name="donePort"></param>
        /// <returns></returns>
        private IEnumerator<ITask> InitializeKnownConnection(Port<bool> donePort)
        {
            bool serviceSuccess = true;

            // Configure default state
            if (_State.SensorPort == vex.SensorPort.SensorPort_None)
            {
                LogError(_State.Name + ": No sensor port selected");
                serviceSuccess = false;
            }
            else
            {
                // Configure default state
                vex.SelectivePortConfigure newConfig = new vex.SelectivePortConfigure();
                newConfig.Body.Port = _State.SensorPort;
                newConfig.Body.Type = vex.SensorDefinition.SensorType.AnalogInput;

                _vexPort.Post(newConfig);
                yield return Arbiter.Choice(newConfig.ResponsePort,
                    delegate(DefaultReplaceResponseType response)
                    {
                    },
                    delegate(Fault fault)
                    {
                        serviceSuccess = false;
                        LogError(LogGroups.Console, "Failed to update VEX service configuration", fault);
                    });

                // Subscribe to VEX for notifications
                if (serviceSuccess)
                {
                    serviceSuccess = false;
                    SaveState(_State);
                    Port<bool> subscribeDonePort = new Port<bool>();
                    SpawnIterator<Port<bool>>(subscribeDonePort, SubscribeToVEX);
                    yield return (Arbiter.Receive<bool>(false, subscribeDonePort,
                        delegate(bool success)
                        {
                            serviceSuccess = success;
                        }));
                }
            }

            donePort.Post(serviceSuccess);
            yield break;
        }

        /// <summary>
        /// Subscribe to appropriate sensor type and port on VEX
        /// Hook up notifications
        /// and then post success or failure
        /// <param name="successPort"></param>
        /// </summary>
        private IEnumerator<ITask> SubscribeToVEX(Port<bool> successPort)
        {
            // Create a custom subscription request
            vex.CustomSubscribeRequestType request = new vex.CustomSubscribeRequestType();

            // Select only the sensor and port we want
            // NOTE: this name must match the VEX sensor name.
            request.Sensors = new List<vex.SensorDefinition>();
            vex.SensorDefinition sensor = new vex.SensorDefinition();
            sensor.Type = vex.SensorDefinition.SensorType.AnalogInput;
            sensor.Port = _State.SensorPort;
            request.Sensors.Add(sensor);

            // Subscribe to the VEX and wait for a response
            yield return
                Arbiter.Choice(_vexPort.SelectiveSubscribe(request, _vexNotificationPort),
                    delegate(SubscribeResponseType Rsp)
                    {
                        base.MainPortInterleave.CombineWith(new Interleave(
                            new ExclusiveReceiverGroup(
                                Arbiter.Receive<vex.Update>(true, _vexNotificationPort, SensorNotificationHandler)
                            ),
                            new ConcurrentReceiverGroup()));

                        // Update our state with subscription status
                        successPort.Post(true);
                    },
                    delegate(Fault fault)
                    {
                        LogError("Analog Input subscription failed", fault);
                        successPort.Post(false);
                    }
                );

            yield break;
        }

        #region Internal Port Handlers

        /// <summary>
        /// Handle sensor update message from VEX
        /// </summary>
        private void SensorNotificationHandler(vex.Update notify)
        {
            if (_State.SensorPort != vex.SensorPort.SensorPort_None)
            {
                // Update state
                int newValue;
                newValue = notify.Body.PortSensors.Sensors[(int)_State.SensorPort - 1].Value;
                bool changed = (_State.RawMeasurement != newValue);
                _State.TimeStamp = DateTime.Now;
                _State.RawMeasurement = newValue;

                // Notify subscribers on any state change
                if (changed)
                {
                    pxanalogsensor.AnalogSensorState genericState = new pxanalogsensor.AnalogSensorState();
                    SendNotification<pxanalogsensor.Replace>(_subMgrPort, _State.ToGenericState(genericState));
                }
            }
        }
        #endregion
        
        #region Handlers

        /// <summary>
        /// Get Handler
        /// </summary>
        /// <param name="get"></param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Concurrent)]
        public virtual IEnumerator<ITask> GetHandler(Get get)
        {
            get.ResponsePort.Post(_State);
            yield break;
        }
        [ServiceHandler(ServiceHandlerBehavior.Concurrent, PortFieldName = "_genericPort")]
        public virtual IEnumerator<ITask> GetHandler(pxanalogsensor.Get get)
        {
            pxanalogsensor.AnalogSensorState genericState = new pxanalogsensor.AnalogSensorState();
            get.ResponsePort.Post(_State.ToGenericState(genericState));
            yield break;
        }

        /// <summary>
        /// HttpGet Handler
        /// </summary>
        /// <param name="get"></param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Concurrent)]
        [ServiceHandler(ServiceHandlerBehavior.Concurrent, PortFieldName = "_genericPort")]
        public virtual IEnumerator<ITask> HttpGetHandler(dssphttp.HttpGet get)
        {
            get.ResponsePort.Post(new dssphttp.HttpResponseType(HttpStatusCode.OK, _State, _transform));
            yield break;
        }
        

        /// <summary>
        /// HttpPost Handler
        /// </summary>
        /// <param name="submit"></param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
        public virtual IEnumerator<ITask> HttpPostHandler(dssphttp.HttpPost httpPost)
        {
            // Use helper to read form data
            ReadFormData readForm = new ReadFormData(httpPost);
            _httpUtilities.Post(readForm);

            // Wait for result
            Activate(Arbiter.Choice(readForm.ResultPort,
                delegate(NameValueCollection parameters)
                {
                    if (parameters["Name"] != null)
                    {
                        // Update our state variables
                        _State.Name = parameters["Name"];
                    }
                    HttpPostSuccess(httpPost);
                },
                delegate(Exception fault)
                {
                    LogError(fault.Message);
                    HttpPostFailure(httpPost, fault.Message);
                })
            );
            yield break;
        }

        /// <summary>
        /// Send Http Post Success Response
        /// </summary>
        /// <param name="httpPost"></param>
        private void HttpPostSuccess(dssphttp.HttpPost httpPost)
        {
            dssphttp.HttpResponseType rsp =
                new dssphttp.HttpResponseType(HttpStatusCode.OK, _State, _transform);
            httpPost.ResponsePort.Post(rsp);
        }

        /// <summary>
        /// Send Http Post Failure Response
        /// </summary>
        /// <param name="httpPost"></param>
        /// <param name="failureReason"></param>
        private static void HttpPostFailure(dssphttp.HttpPost httpPost, string failureReason)
        {
            dssphttp.HttpResponseType rsp =
                new dssphttp.HttpResponseType(HttpStatusCode.ExpectationFailed, failureReason);
            httpPost.ResponsePort.Post(rsp);
        }

        /// <summary>
        /// Replace Handler
        /// </summary>
        /// <param name="replace"></param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
        [ServiceHandler(ServiceHandlerBehavior.Exclusive, PortFieldName = "_genericPort")]
        public virtual IEnumerator<ITask> ReplaceHandler(pxanalogsensor.Replace replace)
        {
            if ((int)_State.SensorPort != replace.Body.HardwareIdentifier)
            {
                Fault fault = Fault.FromException(new InvalidOperationException("Cannot reassign sensor ports"));
                replace.ResponsePort.Post(fault);
                yield break;
            }
            _State.FromGenericState(replace.Body);
            SaveState(_State);

            pxanalogsensor.AnalogSensorState genericState = new pxanalogsensor.AnalogSensorState();
            SendNotification<pxanalogsensor.Replace>(_subMgrPort, _State.ToGenericState(genericState));
            replace.ResponsePort.Post(DefaultReplaceResponseType.Instance);
            yield break;
        }


        /// <summary>
        /// Subscribe Handler
        /// </summary>
        /// <param name="subscribe"></param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
        [ServiceHandler(ServiceHandlerBehavior.Exclusive, PortFieldName = "_genericPort")]
        public virtual IEnumerator<ITask> SubscribeHandler(pxanalogsensor.Subscribe subscribe)
        {
            yield return Arbiter.Choice(
                SubscribeHelper(_subMgrPort, subscribe.Body, subscribe.ResponsePort),
                delegate(SuccessResult success)
                {
                    pxanalogsensor.AnalogSensorState genericState = new pxanalogsensor.AnalogSensorState();
                    SendNotification<pxanalogsensor.Replace>(_subMgrPort, subscribe.Body.Subscriber, new pxanalogsensor.Replace(_State.ToGenericState(genericState)));
                },
                null
            );
            yield break;
        }


        /// <summary>
        /// Generic ReliableSubscribe Handler
        /// </summary>
        /// <param name="subscribe"></param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
        [ServiceHandler(ServiceHandlerBehavior.Exclusive, PortFieldName = "_genericPort")]
        public virtual IEnumerator<ITask> ReliableSubscribeHandler(pxanalogsensor.ReliableSubscribe subscribe)
        {
            yield return Arbiter.Choice(
                SubscribeHelper(_subMgrPort, subscribe.Body, subscribe.ResponsePort),
                delegate(SuccessResult success)
                {
                    pxanalogsensor.AnalogSensorState genericState = new pxanalogsensor.AnalogSensorState();
                    SendNotification<pxanalogsensor.Replace>(_subMgrPort, subscribe.Body.Subscriber, new pxanalogsensor.Replace(_State.ToGenericState(genericState)));
                },
                null
            );
            yield break;
        }

        
        /// <summary>
        /// Drop Handler
        /// </summary>
        /// <param name="drop"></param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Teardown)]
        [ServiceHandler(ServiceHandlerBehavior.Teardown, PortFieldName = "_genericPort")]
        public virtual IEnumerator<ITask> DropHandler(DsspDefaultDrop drop)
        {
            // detach from the brick
            // FIX ME _vexPort.Detach(ServiceInfo.Service);

            // drop the service
            base.DefaultDropHandler(drop);
            yield break;
        }
        #endregion
    }
}
